﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.IdentityModel.Tokens;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.ServiceModel.Security.Tokens;
using System.Collections.ObjectModel;
using System.IdentityModel.Claims;

namespace SecurityTokenServiceNS
{
	public abstract class SecurityTokenService : ISecurityTokenService0502, ISecurityTokenService13
	{
		private string _stsName;
		private string _actionReply;
		private XNamespace _namespaceUri;

		protected SecurityTokenService(string stsName)
		{
			_stsName = stsName;
		}

		public virtual Message ProcessRequestSecurityToken(Message message)
		{
			ValidateMessage(message);

			// Get the RST from the message
			RST rst = CreateFrom(message.GetReaderAtBodyContents(), _namespaceUri);

			RSTR rstr = new RSTR(rst);

			// Generate the Proof Token
			SecurityKeyIdentifier proofKeyIdentifier = GenerateProof(rst, rstr);

			// Set up the claims that are to be issued
			Collection<Claim> claims = GetIssuedClaims(rst);

			// Generate the Security Token to be issued
			GenerateIssuedToken(_stsName, proofKeyIdentifier, claims, rstr);

			// Create a message from the RSTR
			Message rstrMessage = Message.CreateMessage(message.Version, _actionReply, rstr);
			rstrMessage.Headers.RelatesTo = message.Headers.MessageId;

			Console.WriteLine("Outgoing Message...");
			return rstrMessage;
		}

		#region ValidateMessage
		private void ValidateMessage(Message message)
		{
			if (message.Headers.MessageId == null)
				throw new FaultException("STS Exception: The request message does not have a message ID.");

			if (message.Headers.Action == Constants.WSTrust.Actions0502.Issue)
			{
				Console.WriteLine("\nIncomming WS-Trust[0502] Message...");
				_namespaceUri = Constants.WSTrust.NamespaceUri0502;
				_actionReply = Constants.WSTrust.Actions0502.IssueReply;
			}
			else
			{
				Console.WriteLine("\nIncomming WS-Trust[1.3] Message...");
				_namespaceUri = Constants.WSTrust.NamespaceUri13;
				_actionReply = Constants.WSTrust.Actions13.IssueFinalReply;
			}
		} 
		#endregion

		#region ExtensionPoints

		private RST CreateFrom(XmlReader xr, XNamespace namespaceUri)
        {
            XElement xRst = XElement.ReadFrom(xr) as XElement;
			RST rst = new RST(namespaceUri);

			foreach (IRSTProcessor rstp in AddInTool.RstProcessorModules)
				rstp.ProcessMessage(rst, xRst);

            Console.WriteLine("RST successfully created.");
            return rst;
        }

		private SecurityKeyIdentifier GenerateProof(RST rst, RSTR rstr)
		{
			Console.WriteLine("Generating Proof Token...");
			return AddInTool.ProofProcessorModule.GenerateProof(rst, rstr);
		}

		private Collection<Claim> GetIssuedClaims(RST rst)
		{
			Console.WriteLine("Obtaining claims to be issued...");

			Collection<Claim> claims = new Collection<Claim>();

			foreach (IIssuedClaimsProcessor icp in AddInTool.IssuedClaimsProcessorModules)
				icp.GetIssuedClaims(rst, claims);

			if (claims.Count == 0)
				throw new FaultException("STS Exception: The processed issued claims set is empty!");
			return claims;
		}

		private void GenerateIssuedToken(string stsName, SecurityKeyIdentifier proof, Collection<Claim> claims, RSTR rstr)
		{
			Console.WriteLine("Generating Token to be issued...");

			foreach (IIssuedTokenProcessor itp in AddInTool.IssuedTokenProcessorModules)
				itp.GenerateIssuedToken(stsName, proof, claims, rstr);

			if (rstr.RequestedSecurityToken == null)
				throw new FaultException("STS Exception: No available processor for the requested token type!");
		}

		#endregion
	}
}
